﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections;

namespace Objects
{
    public interface IChangingObservable : INotifyPropertyChanging
    {
        IChangingObserver GetObserver();
    }

    public interface IChangingObserver : INotifyPropertyChanging, IDisposable
    {
        object Object { get; }
        bool IsRollback { get; }
        /// <summary>
        /// Полный откат изменений
        /// </summary>        
        void Fullback();
        /// <summary>
        /// Шаговый откат изменений
        /// </summary>        
        void Rollback();
        /// <summary>
        /// Удаление последней точки сохранения (без отката)
        /// </summary>        
        bool RemoveCheckPoint();
        /// <summary>
        /// Откат к последней точке сохранения
        /// </summary>        
        bool RollbackToCheckPoint();
        /// <summary>
        /// Получить наблюдателя с текущего состояния
        /// </summary>
        /// <returns></returns>
        IChangingObserver GetFromCurrent();
        void SetCheckPoint();
        void ForceNotifyChanging(string propName);
    }

    public abstract class ChangingObserver : IChangingObserver
    {
        protected class State
        {
            public bool IsCheckPoint = false;
            public static readonly State CheckPoint = new State() { IsCheckPoint = true };
        }

        protected Type TypeObj;

        private List<State> undo;

        protected void Shot(State state)
        {
            undo.Add(state);
            OnPropertyChanging("IsRollback");
        }

        protected bool Listening;

        private State Pop()
        {
            int cnt = undo.Count - 1;
            State res = undo[cnt];
            undo.RemoveAt(cnt);
            return res;
        }

        /// <summary>
        /// Контракт: index >= 0 && index.LessThan(states.Count)
        /// </summary>
        /// <param name="index"></param>
        /// <returns>Была достигнута точка сохранения</returns>
        protected virtual bool Stateup(State state)
        {
            return state.IsCheckPoint;
        }

        #region INotifyPropertyChanging Members

        public abstract void ForceNotifyChanging(string propName);

        public void OnPropertyChanging(string propName)
        {
            if (PropertyChanging != null)
                PropertyChanging(this, new PropertyChangingEventArgs(propName));
        }

        public event PropertyChangingEventHandler PropertyChanging;

        #endregion

        #region IDisposable Members

        public virtual void Dispose()
        {
            PropertyChanging = null;
            undo = null;
            TypeObj = null;
            Object = null;
        }

        #endregion

        protected ChangingObserver(object stateObject)
        {
            undo = new List<State>();
            Listening = true;
            this.Object = stateObject;
        }

        #region IChangingObserver Members

        public virtual object Object
        {
            get
            {
                return null;
            }
            protected set
            {
                if (value != null)
                    TypeObj = value.GetType();
                else
                    TypeObj = null;
            }
        }

        public bool IsRollback
        {
            get
            {
                return undo.Count > 0;
            }
        }

        public void Fullback()
        {
            Listening = false;
            try
            {
                while (IsRollback)
                    Stateup(Pop());
            }
            finally
            {
                Listening = true;
            }
        }

        public void Rollback()
        {
            Listening = false;
            try
            {
                if (IsRollback)
                    Stateup(Pop());
                else
                    throw new Exception("Невозможно выполнить шаговый откат");
            }
            finally
            {
                Listening = true;
            }
        }

        public abstract IChangingObserver GetFromCurrent();

        public bool RollbackToCheckPoint()
        {
            Listening = false;
            bool res = false;
            try
            {
                while (IsRollback && !(res = Stateup(Pop())))
                { }
            }
            finally
            {
                Listening = true;
            }
            return res;
        }

        public void SetCheckPoint()
        {
            undo.Add(State.CheckPoint);
        }

        public bool RemoveCheckPoint()
        {
            int ind = undo.LastIndexOf(State.CheckPoint);
            if (ind >= 0)
                undo.RemoveAt(ind);
            return ind >= 0;
        }

        #endregion
    }
}
